package com.borqs.music.lyric;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class ID3V2 {

    /*
     * public static void main(String[] args) { try { // URL url = new
     * URL("http://localhost:8080/ddd.mp3"); // URLConnection conn =
     * url.openConnection(); InputStream is = new FileInputStream(new
     * File("e:\\qunxing.mp3")); ID3V2 id3 = new ID3V2(is); String s =
     * id3.uslt(); if (s != null) { System.out.println(s); byte[] data =
     * s.getBytes("UTF-8"); Lyric l = new Lyric(data); l.seekByTime(13 * 1000 +
     * 21 * 11); System.out.println(l.getCurrentLine());
     * System.out.println(l.getNextLine()); }
     *  } catch (Exception e) { e.printStackTrace(); } }
     */

    // the InputStream of the MP3 file.it can be got from local file or http url
    private InputStream is;

    private int tagSize = -1;// ID3V2 size

    // here we store all the tag included in ID3V2.
    private Map<String, byte[]> tags = new HashMap<String, byte[]>();

    public ID3V2(byte[] data) throws IOException, ParseException {
        this(new ByteArrayInputStream(data));
    }

    public ID3V2(InputStream _is) throws IOException, ParseException {
        this.is = _is;
        if (is == null)
            throw new NullPointerException("InputStream of mp3 can not be null");
        // read the header first
        byte[] header = new byte[10];
        is.read(header);
        // here we only check ID3
        if (header[0] != 'I' || header[1] != 'D' || header[2] != '3') {
            throw new ParseException("not invalid mp3 ID3 tag");
        }
        // calulate the size
        tagSize = (header[9] & 0xff) + ((header[8] & 0xff) << 7) + ((header[7] & 0xff) << 14)
                + ((header[6] & 0xff) << 21);
        // ok let's parse the ID3V2 tag
        int pos = 10;
        while (pos < tagSize) {
            byte[] tag = new byte[10];
            is.read(tag);
            if (tag[0] == 0) {
                // id should not start with 0,it's a padding,so break.
                break;
            }
            // the name of the tag
            String tagName = new StringBuffer().append((char) tag[0]).append((char) tag[1]).append((char) tag[2])
                    .append((char) tag[3]).toString();
            // the data's length of the tag
            //
            // int length = (int) (tag[4] * 0x100000000l + tag[5] * 0x10000l
            // + tag[6] * 0x100l + tag[7]);
            int length = byteArrayToLong(tag, 4, 4);
            if (length > tagSize - pos) {
                // some errors happened,break
                break;
            }
            byte[] data = new byte[length];
            is.read(data);
            tags.put(tagName, data);
            pos = pos + length + 10;
        }
        is.close();
    }

    /**
     * get the Unsynchronized lyric text
     *
     * @return the lyric or null
     */
    public String uslt() {
        byte[] data = (byte[]) tags.get("USLT");
        if (data == null)
            return null;
        Parser parser = new Parser(data, true);
        try {
            byte[] type = parser.parseBinary(3);
            // ignore the type
            String t = new String(type, "ISO8859_1");
            // ignore the description
            parser.parseText();
            return parser.parseText();

        } catch (Exception e) {
            return null;
        }
    }

    public int byteArrayToLong(byte[] in, int start, int length) {
        int value = 0;

        for (int i = start; i < (start + length); i++) {
            // move each byte (length-pos-1)*8 bits to the left and add them
            value += (long) ((in[i] & 0xff) << ((length - i + start - 1) * 8));
        }

        return value;
    }

    /**
     * the parser to parse the tag content of ID3V2
     */
    class Parser {

        private byte[] in;

        private int start;

        private int stop;

        private int pos; // Next byte to parse

        private byte encoding; // Encoding used for text

        public final static byte ISO = 0;

        public final static byte UNICODE = 1;// UTF-16

        public Parser(byte[] in, boolean encoding) {
            this(in, encoding, 0, in.length - 1);
        }

        public Parser(byte[] in, boolean encoding, int start, int stop) {
            this.in = in;
            this.start = start;
            this.pos = start;
            this.stop = stop;

            if (encoding == true) {
                parseEncoding();
            } else {
                this.encoding = ISO;
            }
        }

        public void setPosition(int pos) {
            this.pos = pos;
        }

        public int getPosition() {
            return pos;
        }

        public void parseEncoding() {
            encoding = in[pos];
            pos++;
        }

        public String parseText() throws ParseException {
            return parseText(this.encoding);
        }

        public String parseText(byte encoding) throws ParseException {
            try {
                // find termination
                int term = pos;

                if (encoding == ISO) {
                    while (in[term] != 0 && term < stop) {
                        term++;
                    }
                } else {
                    // should check three zero byte as seperator
                    while (term < stop - 2 && (in[term] != 0 || in[term + 1] != 0 || in[term + 2] != 0)) {
                        term++;
                    }
                }

                // if text is terminated by end of byte array, term must be
                // behind
                // last index
                if ((encoding == ISO && term == stop && in[term] != 0)
                        || (encoding == UNICODE && term == stop - 2 && (in[term] != 0 || in[term + 1] != 0 || in[term + 2] != 0))) {

                    term = stop;
                }

                // convert
                String ret = null;
                try {
                    ret = new String(in, pos, term - pos + 1, (encoding == ISO ? "ISO-8859-1" : "UTF-16"));
                } catch (java.io.UnsupportedEncodingException e) {
                    // cannot happen
                    e.printStackTrace();
                }

                // advance position marker
                pos = term + (encoding == ISO ? 1 : 3);

                return ret;
            } catch (Exception e) {
                e.printStackTrace();
                throw new ParseException("Parse text failed");
            }
        }

        public byte[] parseBinary() throws ParseException {
            return parseBinary(stop - pos + 1);
        }

        public byte[] parseBinary(int number) throws ParseException {
            try {
                byte[] ret = new byte[number];
                System.arraycopy(in, pos, ret, 0, number);
                pos += number; // no more reading possible...
                return ret;
            } catch (Exception e) {
                throw new ParseException("Parse binary failed");
            }
        }

    }
}
